﻿//Copyright (C) <2013>  <jonathan cleeve norton> All Rights Reserved 
//Contact jon.norton@fin-plus.co.uk website <http://www.fin-plus.co.uk/>
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using FinPlusInterfaces;

namespace FinPlusAssembler
{
    public static class TypeExtension
    {
        private delegate T Constructor<T>(params object[] args);
        private static Dictionary<string, Dictionary<string, Delegate>> _cache = new Dictionary<string, Dictionary<string, Delegate>>();

        //public
        public static T Construct<T>(this Type type, object[] parms)
        {
            var constructor = (Constructor<T>)type.GetActivator<T>(parms);
            return constructor(parms);
        }

        public static bool IsTypeOf<T>(this Type type)
        {
            var interfaceType = typeof(T);

            if (interfaceType.Name.Equals(type.Name.Split('`')[0]))
                return true;

            return interfaceType.IsAssignableFrom(type);
        }

        public static object ChangeType(this Type type, string value)
        {
            try
            {
                if (type.Equals(typeof(DateTime)))
                    return value.DateTimeParse();

                if (type.Equals(typeof(Int32)))
                    return value.IntParse();

                if (type.IsTypeOf<IPod>())
                    return IOC.New<IPod>(value);

                return Convert.ChangeType(value, type);
            }
            catch
            {
                return value;
            }
        }

        public static string[] StatePropertys(this Type type, bool test)
        {
            var properties = type.GetProperties().Where(p => p.GetCustomAttributes<StateAttribute>().Any(a => test ? a.Test : a.Save));
            if (properties == null)
                return null;
            return properties.Select(p => p.Name).ToArray();
        }

        //private
        private static Delegate GetActivator<T>(this Type type, object[] parms)
        {
            IDictionary<string, Delegate> typeCache;
            if (_cache.ContainsKey(type.Name))
                typeCache = _cache[type.Name];
            else
                typeCache = new Dictionary<string, Delegate>();

            var key = string.Join("~", parms.Select(p => p == null ? "null" : p.GetType().Name).ToArray());
            if (typeCache.ContainsKey(key))
                return typeCache[key];

            var del = GetExpression<T>(type, parms);
            typeCache[key] = del;

            return del;
        }

        private static Delegate GetExpression<T>(this Type type, object[] parms)
        {
            var cstrInfo = GetConstructor(type, parms);
            var parmsInfo = cstrInfo.GetParameters();
            var parm = Expression.Parameter(typeof(object[]), "args");
            var parmExp = new Expression[parmsInfo.Length];

            for (int i = 0; i < parmsInfo.Length; i++)
            {
                var paramType = parmsInfo[i].ParameterType;
                var index = Expression.ArrayIndex(parm, Expression.Constant(i));
                parmExp[i] = Expression.Convert(index, paramType);
            }

            return Expression.Lambda(typeof(Constructor<T>), Expression.New(cstrInfo, parmExp), parm).Compile();
        }

        private static ConstructorInfo GetConstructor(this Type type, object[] parms)
        {
            var parmsLen = parms.Count();
            var cstrs = type.GetConstructors().Where(c => c.GetParameters().Count().Equals(parmsLen));

            foreach (var cstrInfo in cstrs)
            {
                var ctrsParm = cstrInfo.GetParameters();

                if (parmsLen == 0)
                    return cstrInfo;

                if (IsValid(parms, ctrsParm, parmsLen))
                    return cstrInfo;
            }

            return null;
        }

        private static bool IsValid(object[] parms, ParameterInfo[] parmInfos, int parmsLen)
        {
            int i = -1;
            foreach (var parm in parms)
            {
                i++;
                var infoType = parmInfos[i].ParameterType;
                if (parm == null)
                    continue;

                var parmType = parm.GetType();
                if (infoType.Equals(parmType))
                    continue;

                if (infoType.IsAssignableFrom(parmType))
                    continue;

                return false;
            }

            return true;
        }
    }
}
